package com.leyou.search.service.impl;

import com.leyou.dto.PageDTO;
import com.leyou.exception.LyException;
import com.leyou.item.clients.ItemClient;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.dto.SearchParamDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.IndexService;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author ：qcq
 * @date ：Created in 2020/12/13 9:30
 * @description：
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private IndexService indexService;

    @Override
    public Mono<List<String>> getSuggestion(String key) {

        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "查询参数不能为空");
        }

        return goodsRepository.suggestBySingleField("suggestion", key);
    }

    @Override
    public Mono<PageInfo<Goods>> searchGoods(SearchParamDTO searchParamDTO) {

        //分页page
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(getQueryBuilder(searchParamDTO));

        searchSourceBuilder.from(searchParamDTO.getFrom());
        searchSourceBuilder.size(searchParamDTO.getSize());

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<am>");
        highlightBuilder.postTags("</am>");
        highlightBuilder.field("title");
        searchSourceBuilder.highlighter(highlightBuilder);

        //过滤filters
        searchSourceBuilder.fetchSource(new String[]{"id", "title", "image", "prices", "sold"}, new String[0]);


        //排序desc
        Set<String> sortCondition = new HashSet<>();
        sortCondition.add("prices");
        sortCondition.add("sold");
        sortCondition.add("updateTime");

        if (StringUtils.isNotBlank(searchParamDTO.getSortBy())&&sortCondition.contains(searchParamDTO.getSortBy())) {
            searchSourceBuilder.sort(searchParamDTO.getSortBy(), searchParamDTO.getDesc() ? SortOrder.DESC : SortOrder.ASC);
        }





        return goodsRepository.queryBySourceBuilderForPageHighlight(searchSourceBuilder);
    }

    private QueryBuilder getQueryBuilder(SearchParamDTO searchParamDTO) {
        //条件key
        String key = searchParamDTO.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(400, "查询参数不能为空");
        }
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //给boolean添加查询条件filterEntry.getKey()
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", key).operator(Operator.AND));
        //过滤条件是否添加，取决于filterMap中是否有值
        if (!CollectionUtils.isEmpty(searchParamDTO.getFilters())) {
            for (Map.Entry<String, String> filterEntry : searchParamDTO.getFilters().entrySet()) {
                //过滤条件的key值
                String queryKey = filterEntry.getKey();

                //过滤条件的value值
                String queryValue = filterEntry.getValue();

                //key替换为brandId或者categoryId
                if ("品牌".equals(queryKey)) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", queryValue));
                } else if ("分类".equals(queryKey)) {
                    boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId", queryValue));
                } else {//其他参数，查询本身nested查询
                    BoolQueryBuilder subBoolean = QueryBuilders.boolQuery();
                    subBoolean.must(QueryBuilders.termQuery("specs.name", queryKey));
                    subBoolean.must(QueryBuilders.termQuery("specs.value", queryValue));
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("specs", subBoolean, ScoreMode.None));
                }
            }
        }

        return boolQueryBuilder;
    }

    @Override
    public Mono<Map<String, List<?>>> listFilter(SearchParamDTO searchRequest) {

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        sourceBuilder.query(getQueryBuilder(searchRequest));
        //需要查询但是不需要查询结果
        sourceBuilder.size(0);

        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(30));
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(30));

        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg", "specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(30)
                                        .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(30)))
        );
        Mono<Aggregations> aggregationsMono = goodsRepository.aggregationBySourceBuilder(sourceBuilder);
        Mono<Map<String, List<?>>> map = aggregationsMono.map(aggregations -> {
            Map<String, List<?>> filterResult = new HashMap<>();

            Terms categoryAgg = aggregations.get("categoryAgg");
            List<Long> categoryIds = categoryAgg.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsNumber)
                    .map(Number::longValue)
                    .collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(categoryIds)) {
                filterResult.put("分类", this.itemClient.queryCategoryByIdList(categoryIds));
            }

            Terms brandAgg = aggregations.get("brandAgg");
            List<Long> brandIds = brandAgg.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsNumber)
                    .map(Number::longValue)
                    .collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(brandIds)) {
                filterResult.put("品牌", this.itemClient.queryBrandListByIds(brandIds));
            }

            Nested specAgg = aggregations.get("specAgg");

            Terms nameAgg = specAgg.getAggregations().get("nameAgg");
            nameAgg.getBuckets().forEach(bucket -> {
                String resultKey = bucket.getKeyAsString();
                Terms valueAgg = bucket.getAggregations().get("valueAgg");
                List<String> resultValues = valueAgg.getBuckets()
                        .stream()
                        .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                        .collect(Collectors.toList());
                filterResult.put(resultKey, resultValues);
            });

            return filterResult;
        });
        return map;
    }

    @Override
    public void saveGoodsById(Long spuId) {
//        SpuDTO spuDTO = itemClient.querySkuAndSpuAndApuDetailById(spuId);
        PageDTO<SpuDTO> pageDTO = this.itemClient.findPage(1,1,true,null,null,spuId);
        SpuDTO spuDTO = pageDTO.getItems().get(0);
        Goods goods = indexService.buildGoods(spuDTO);
        goodsRepository.save(goods);
    }
    public Goods buildGoods(SpuDTO spuDTO)  {
        Goods goods = new Goods();
        BeanUtils.copyProperties(spuDTO, goods);
        return goods;
    }

    @Override
    public void deleteGoodsById(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
